﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using MapLibrary;
using Animation;
using MapLibrary.DataLoading;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using MapLibrary.Players;
using MapLibrary.MapLibrary.DataLoading;
using MapLibrary.AILibrary.Pathfinding;
using UtilityLibrary;
using FarseerPhysics;
/**
 * This class is responsible for loading the level data, which includes the map data
 * and the sprite data.
 */
namespace MapLibrary
{
    public class Level
    {

        private Map mMap;
        private SpritePalette mSpriteTypes;
        private GameWorld world;
        private Player player;

        /// <summary>
        /// bodies to be removed from the physics world at the end of the frame
        /// </summary>
        private List<Sprite> bodiesToRemove;
        /// <summary>
        /// bodies to be disabled at the end of the frame
        /// </summary>
        private List<Sprite> bodiesToDisable;
        /// <summary>
        /// bodies to be enabled at the end of the frame
        /// </summary>
        private List<Sprite> bodiesToEnable;

        public Player Player
        {
            get { return player; }
        }

        /// <summary>
        /// The Map that the Level uses.
        /// </summary>
        [ContentSerializerIgnore]
        public Map Map
        {
            get { return mMap; }
        }

        /// <summary>
        /// The list of SpriteTypes that can be seen in the level.
        /// </summary>
        public SpritePalette SpriteTypes
        {
            get { return mSpriteTypes; }
            set { mSpriteTypes = value; }
        }

        /// <summary>
        /// Name of the Map that the level uses.
        /// </summary>
        public string MapName { get; set; }

        /// <summary>
        /// The Sprites in the Level.
        /// </summary>
        public List<Sprite> Sprites
        {
            get;
            set;
        }

        /// <summary>
        /// The static colliding regions for the level.
        /// </summary>
        [ContentSerializerIgnore]
        public List<LevelCollisionRegion> LevelCollisionRegions
        {
            get;
            set;
        }

        /// <summary>
        /// Graph used for pathfinding
        /// </summary>
        [ContentSerializerIgnore]
        public Graph LevelGraph { get; set; }

        public GameWorld World
        {
            get { return world; }
        }

        public Level(GameWorld world)
        {
            Sprites = new List<Sprite>();
            LevelCollisionRegions = new List<LevelCollisionRegion>();
            bodiesToRemove = new List<Sprite>();
            bodiesToEnable = new List<Sprite>();
            bodiesToDisable = new List<Sprite>();
            this.world = world;

            LevelGraph = new Graph(150, 150, 100, -900, -3000);

        }

        /// <summary>
        /// Adds a new LevelCollisionRegion to the level, using the data
        /// from the passed CollisionData.
        /// </summary>
        /// <param name="obj"></param>
        public void AddCollisionRegion(LevelCollisionData obj)
        {
            this.LevelCollisionRegions.Add(
                LevelCollisionRegion.CreateLevelCollisionRegion(world.PhysicsWorld, obj));
        }

        /// <summary>
        /// Add a Sprite to the level, as defined by SpriteType.  The sprite
        /// will be placed at location pos.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="pos"></param>
        public void AddSprite(SpriteType type, Vector2 pos)
        {
            Sprite s = SpriteFactory.CreateSprite(world,type,pos);
            if (s.Bot != null)
            {
                s.Bot.Init(world, world.ContentManager);
            }
            Sprites.Add(s);
        }

        /// <summary>
        /// Add a sprite to the level
        /// </summary>
        /// <param name="sprite"></param>
        public void AddSprite(Sprite sprite)
        {
            Debug.Assert(!Sprites.Contains(sprite));
            Sprites.Add(sprite);
        }

        /// <summary>
        /// Remove a sprite from the level
        /// </summary>
        /// <param name="sprite"></param>
        public void RemoveSprite(Sprite sprite)
        {
            Sprites.Remove(sprite);
        }

        /// <summary>
        /// Place the player into the level, and save a reference
        /// to it.
        /// </summary>
        /// <param name="p"></param>
        public void RegisterPlayer(Player p)
        {
            player = p;
            Sprites.Add(p);
        }

        /// <summary>
        /// Gets the information only LevelData for this Level.  LevelData
        /// is an object that is separate from the physics engine, and is
        /// used for saving/loading the level.
        /// </summary>
        /// <returns></returns>
        public LevelData GetLevelData()
        {
            LevelData data = new LevelData();

            data.SpritePalette = this.SpriteTypes;
            data.MapName = this.MapName;
            data.SpriteData = new List<SpriteData>();

            foreach (Sprite s in Sprites)
            {
                data.SpriteData.Add(s.GetSpriteData());
            }

            data.CollisionData = new List<LevelCollisionData>();

            foreach (LevelCollisionRegion r in LevelCollisionRegions)
            {
                data.CollisionData.Add(r.GetCollisionData());
            }

            

            return data;
        }

        /// <summary>
        /// Draw the Level onto the screen.
        /// </summary>
        /// <param name="Batch"></param>
        /// <param name="Viewport"></param>
        public void Draw(SpriteBatch Batch, WorldViewport Viewport)
        {
            mMap.Draw(Batch, Viewport);
            foreach (Sprite s in Sprites)
            {
                s.Draw(Batch, Viewport);
            }
            //LevelGraph.draw(Batch, Viewport);
            /*List<Node> path = new List<Node>();
            if (LevelGraph.GetPath(player,Sprites[Sprites.Count - 1], ref path))
            {
                for(int i=0;i<path.Count;i++)
                {
                    PrimitiveBrush.RenderPoint(Batch, Viewport.WorldToViewportCoords(path[i].Position));
                    if (i != path.Count - 1)
                    {
                        PrimitiveBrush.RenderLine(Batch, Viewport.WorldToViewportCoords(path[i].Position), Viewport.WorldToViewportCoords(path[i + 1].Position));
                    }
                }
            }*/
        }

        /// <summary>
        /// Initialize the Level.  This involves loading
        /// the map and sprite palette.  Loading Sprites and
        /// LevelCollisionRegions are done in the Register
        /// method.
        /// </summary>
        /// <param name="pData"></param>
        /// <param name="pContent"></param>
        public void Init(LevelData pData, ContentManager pContent)
        {
            mSpriteTypes = pData.SpritePalette;
            MapName = pData.MapName;

            mMap = pContent.Load<Map>(MapName);

        }

        /// <summary>
        /// Registers the Level to the World.  This involves
        /// adding all important Body objects to the World.  After
        /// this stage is complete, the Level can then execute.
        /// </summary>
        /// <param name="pData"></param>
        /// <param name="pWorld"></param>
        /// <param name="pContent"></param>
        public void Register(LevelData pData, GameWorld pWorld, ContentManager pContent)
        {
            //load regions.
            foreach (LevelCollisionData cData in pData.CollisionData)
            {
                LevelCollisionRegion lcr = LevelCollisionRegion.CreateLevelCollisionRegion(
                    pWorld.PhysicsWorld, cData);
                LevelCollisionRegions.Add(lcr);

                LevelGraph.BlockNodesInGround(lcr);
            }

            //load sprites.
            foreach (SpriteData sData in pData.SpriteData)
            {
                Sprite s = SpriteFactory.CreateSprite(pWorld, sData);
                if (s.Bot != null)
                {
                    s.Bot.Init(pWorld, world.ContentManager);
                }

                Sprites.Add(s);

            }
        }

        /// <summary>
        /// Update the animations for all Sprites in the level.
        /// </summary>
        public void UpdateAnimations()
        {
            foreach (Sprite s in Sprites)
            {
                s.Update();
            }
        }

        /// <summary>
        /// flag a sprites body to be removed from the physics world at the end of the frame
        /// </summary>
        /// <param name="s"></param>
        public void AddBodyToRemove(Sprite s)
        {
            if(!bodiesToRemove.Contains(s))
                bodiesToRemove.Add(s);
        }

        /// <summary>
        /// flag a sprites body to be enabled at the end of the frame
        /// </summary>
        /// <param name="s"></param>
        public void AddBodyToEnable(Sprite s)
        {
            bodiesToEnable.Add(s);
        }

        /// <summary>
        /// flag a sprites body to be disabled at the end of the frame
        /// </summary>
        /// <param name="s"></param>
        public void AddBodyToDisable(Sprite s)
        {
            bodiesToDisable.Add(s);
        }

        /// <summary>
        /// Remove, disable or enable bodies for changes that happened in the frame.
        /// </summary>
        /// <param name="world"></param>
        public void PostUpdate(World world)
        {
            foreach (Sprite s in bodiesToEnable)
            {
                s.Body.Enabled = true;
            }
            foreach (Sprite s in bodiesToDisable)
            {
                s.Body.Enabled = false;
            }
            foreach (Sprite s in bodiesToRemove)
            {
                s.DeregisterFromWorld(World);
                RemoveSprite(s);
            }
            world.ProcessChanges();

            bodiesToEnable.Clear();
            bodiesToDisable.Clear();
            bodiesToRemove.Clear();
        }

        public GameWorld GetGameWorld()
        {
            return world;
        }

    }
}
